package com.class5.springboot.frame.repo.real;

import com.class5.springboot.frame.repo.entity.SuperEntity;
import com.class5.springboot.frame.repo.Sql;
import com.class5.springboot.frame.repo.tool.xml.DbTable;
import com.class5.springboot.frame.repo.tool.xml.DbTableColumn;
import com.class5.springboot.frame.util.kengen.KeyGeneratorFactory;
import com.class5.springboot.util.DateUtils;
import com.google.common.base.Preconditions;
import org.springframework.jdbc.core.RowMapper;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 映射
 * @author Administrator
 */
public class EntityMapper {
    //项目数据配置
    private DbTable table;
    //当前实体类
    private Class<?> entityClass;
    //表名
    private String tableName;
    //所有字段
    private List<FieldMapper> fields = new ArrayList<FieldMapper>();
    //按column-name存放
    private Map<String, FieldMapper> fieldMap = new HashMap<String, FieldMapper>();
    //主键字段
    private FieldMapper idField;
    //乐观锁
    private FieldMapper versionField;
    //租户字面
    private FieldMapper tenantField;
    //关联的实体类的Mapper
    private EntityMapper refEntityMapper;
    //数据库
    private RowMapper<Object> jdbcRowMapper = new RowMapper<Object>() {
        @Override
        public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
            try {
                Object entity = entityClass.newInstance();
                for (FieldMapper fm : fields) {
                    Object val = rs.getObject(fm.getColName());
                    fm.setValue(entity, convertSqlObjectToPoFieldObject(val, fm.getType()));
                }
                return entity;
            } catch (Exception e) {
                throw new SQLException("Convert resultSet to " + entityClass.getName() + " error", e);
            }
        }
    };

    public EntityMapper(Class<?> entityClass, DbTable table) {
        this.entityClass = entityClass;
        this.table = table;
        init();
    }

    protected void init() {
        this.tableName = table.getName();
        Field[] fields = entityClass.getDeclaredFields();
        for (Field f : fields) {
            DbTableColumn col = table.getColumnByField(f.getName());
            if (col == null)
                continue;

            FieldMapper fm = new FieldMapper();
            fm.setName(f.getName());
            fm.setType(f.getType());
            fm.setIdentity(col.isIdentity());
            fm.setAutoIncByRepo(col.isAutoIncByRepo());
            fm.setAutoIncrement(col.isAutoIncrement());
            if (fm.isIdentity()) {
                idField = fm;
            }
            fm.setVersion(col.isVersion());
            if (fm.isVersion()) {
                versionField = fm;
            }
            fm.setColName(col.getName());
            fm.setLabel(col.getLabel());
            this.fields.add(fm);
        }
        refEntityMapper = this;
        for (FieldMapper f : this.fields) {
            fieldMap.put(f.getColName(), f);
        }
        Collections.unmodifiableMap(this.fieldMap);
        Collections.unmodifiableCollection(this.fields);
    }

    /**
     * 生成批量新加的代码
     *
     * @param entities
     * @param defProvider
     * @return
     */
    public BatchSql generatorBatchInsert(List<?> entities) {
        //生成语句
        StringBuffer colstr = new StringBuffer();
        StringBuffer qstr = new StringBuffer();
        EntityMapper refmp = getRefEntityMapper();
        for (FieldMapper fm : refmp.getFields()) {
            colstr.append("," + fm.getColName());
            qstr.append(",?");
        }
        String sql = String.format("insert into %s(%s) values(%s)", refmp.getTableName(),
                colstr.substring(1), qstr.substring(1));
        BatchSql binfo = new BatchSql();
        binfo.setSql(sql);
        for (Object entity : entities) {
            List<Object> ls = new ArrayList<Object>();
            for (FieldMapper fm : refmp.getFields()) {
                Object val = null;
                //通过程序生成主键值
                if (fm.isIdentity() && fm.isAutoIncByRepo()) {
                    val = getFieldValueIfExists(fm.getColName(), entity);
                    //用户未设置时自动生成id
                    if (val == null) {
                        val = KeyGeneratorFactory.generateKey();
                        setFieldValueIfExists(fm.getColName(), entity, val);
                    }
                } else if (fm.isVersion()) {
                    //初始化版本号
                    val = 1L;
                    setFieldValueIfExists(fm.getColName(), entity, val);
                } else {
                    val = getFieldValueIfExists(fm.getColName(), entity);
                }
                ls.add(val);
            }
            binfo.addBatchParam(ls);
        }
        return binfo;
    }

    private Object getFieldValueIfExists(String field, Object entity) {
        FieldMapper fm = fieldMap.get(field);
        if (fm != null) {
            return fm.getValue(entity);
        }
        return null;
    }

    private void setFieldValueIfExists(String field, Object entity, Object val) {
        FieldMapper fm = fieldMap.get(field);
        if (fm != null) {
            fm.setValue(entity, val);
        }
    }

    /**
     * 生成批量更新的代码，不支持乐观锁
     *
     * @param entities
     * @return
     */
    public BatchSql generatorBatchUpdate(List<?> entities) {
        //生成语句
        StringBuffer colstr = new StringBuffer();
        EntityMapper refmp = getRefEntityMapper();
        for (FieldMapper fm : refmp.getFields()) {
            if (fm.isIdentity())
                continue;
            if (!fieldMap.containsKey(fm.getColName())
                    && !fm.isVersion())
                continue;
            colstr.append("," + fm.getColName() + "=?");
        }
        String sql = String.format("update %s set %s where %s=?",
                refmp.getTableName(), colstr.substring(1), refmp.getIdField().getColName());
        //生成批量参数
        BatchSql binfo = new BatchSql();
        binfo.setSql(sql);
        for (Object entity : entities) {
            List<Object> ls = new ArrayList<Object>();
            Long id = refmp.getIdField().getValue(entity);
            for (FieldMapper fm : refmp.getFields()) {
                if (fm.isIdentity())
                    continue;
                if (!fieldMap.containsKey(fm.getColName())
                        && !fm.isVersion())
                    continue;

                Object val = null;
                if (fm.isVersion()) {
                    val = KeyGeneratorFactory.generateKey();
                    setFieldValueIfExists(fm.getColName(), entity, val);
                } else {
                    val = getFieldValueIfExists(fm.getColName(), entity);
                }
                ls.add(val);
            }
            ls.add(id);
            binfo.addBatchParam(ls);
        }
        return binfo;
    }

    /**
     * 生成更新语句
     *
     * @param entity
     * @return
     */
    public Sql generatorUpdateById(Object entity, Long id, Long oldVersion, boolean updateNull) {
        Preconditions.checkNotNull(tableName);
        Preconditions.checkNotNull(entity);
        Preconditions.checkNotNull(id);
        StringBuffer colstr = new StringBuffer();
        List<Object> params = new ArrayList<Object>();
        EntityMapper refmp = getRefEntityMapper();
        for (FieldMapper fm : refmp.getFields()) {
            if (fm.isIdentity())
                continue;
            if (!fieldMap.containsKey(fm.getColName())
                    && !fm.isVersion())
                continue;
            //如果实体类继承了SuperEntity，属性如果未改变也不作更新
            if (entity instanceof SuperEntity) {
                SuperEntity sentity = (SuperEntity) entity;
                if (!sentity.isModified(fm.getColName())) {
                    continue;
                }
            }

            Object val = null;
            if (fm.isVersion()) {
                val = KeyGeneratorFactory.generateKey();
                setFieldValueIfExists(fm.getColName(), entity, val);
            } else {
                val = getFieldValueIfExists(fm.getColName(), entity);
            }
            //空时是否更新
			/*if(!updateNull && val == null)
				continue;*/
            colstr.append("," + fm.getColName() + "=?");
            params.add(val);
        }
        //考虑乐观锁
        if (refEntityMapper.getVersionField() != null) {
            String sql = String.format("update %s set %s where %s=? and %s=?", tableName, colstr.substring(1), idField.getColName(), refEntityMapper.getVersionField().getColName());
            params.add(id);
            params.add(Preconditions.checkNotNull(oldVersion));
            return new Sql(sql, params.toArray());
        }
        String sql = String.format("update %s set %s where %s=?", tableName, colstr.substring(1), idField.getColName());
        params.add(id);
        return new Sql(sql, params.toArray());

    }

    /**
     * 生成根据ID删除的语句
     *
     * @param ids
     * @return
     */
    public BatchSql generatorBatchRemoveById(List<Long> ids) {
        if (ids == null || ids.isEmpty())
            return null;
        String sql = String.format("delete from %s where %s=?", refEntityMapper.getTableName(), refEntityMapper.getIdField().getColName());
        BatchSql info = new BatchSql();
        info.setSql(sql);
        for (Long id : ids) {
            info.addBatchParam(Arrays.asList(id));
        }
        return info;
    }

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public String getTableName() {
        return tableName;
    }

    public FieldMapper getIdField() {
        return idField;
    }

    public FieldMapper getVersionField() {
        return versionField;
    }

    public FieldMapper getTenantField() {
        return tenantField;
    }

    @Override
    public String toString() {
        return "EntityMapper [entityClass=" + entityClass + ", tableName="
                + tableName + ", fields=" + fields + "]";
    }

    public boolean hasColumn(String colName) {
        return fieldMap.containsKey(colName);
    }

    public FieldMapper getField(String colName) {
        return fieldMap.get(colName);
    }

    public EntityMapper getRefEntityMapper() {
        return refEntityMapper;
    }

    public List<FieldMapper> getFields() {
        return fields;
    }

    public Map<String, FieldMapper> getFieldMap() {
        return fieldMap;
    }


    public RowMapper<Object> getJdbcRowMapper() {
        return jdbcRowMapper;
    }

    private String columnJoinsFragmentCache = null;

    /**
     * 列的拼接串
     *
     * @return
     */
    public String getColumnJoinsFragment(String columnPrefix) {
        if (columnJoinsFragmentCache != null && columnPrefix == null)
            return columnJoinsFragmentCache;
        StringBuffer sb = new StringBuffer();
        for (FieldMapper fm : fields) {
            sb.append("," + (columnPrefix == null ? "" : columnPrefix + ".") + fm.getColName());
        }
        if (columnPrefix != null) {
            return sb.substring(1);
        }
        columnJoinsFragmentCache = sb.substring(1);
        return columnJoinsFragmentCache;
    }

    /**
     * 将获得的JDBC的列的对象转换为JAVA对象可以识别的类型
     *
     * @param sqlObject
     * @return
     */
    public static Object convertSqlObjectToPoFieldObject(Object sqlObject, Class<?> poFieldType) {
        if (sqlObject == null)
            return null;
        if (sqlObject instanceof BigInteger) {
            if (poFieldType.equals(Integer.class))
                return Integer.valueOf(((BigInteger) sqlObject).intValue());
            if (poFieldType.equals(Long.class))
                return Long.valueOf(((BigInteger) sqlObject).longValue());
        } else if (sqlObject instanceof Long) {
            if (poFieldType.equals(Integer.class))
                return Integer.valueOf(((Long) sqlObject).intValue());
            if (poFieldType.equals(Long.class))
                return (Long) sqlObject;
        } else if (sqlObject instanceof String) {
            if (!poFieldType.equals(String.class)) {
                return DateUtils.parse(poFieldType, sqlObject.toString());
            }
        }
        return sqlObject;
    }
}

